using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Text;

namespace fastJSON
{
	/// <summary>
	/// This class encodes and decodes JSON strings.
	/// Spec. details, see http://www.json.org/
	/// 
	/// JSON uses Arrays and Objects. These correspond here to the datatypes ArrayList and Hashtable.
	/// All numbers are parsed to doubles.
	/// </summary>
	internal class JsonParser
	{
		enum Token
		{
			None = -1,           // Used to denote no Lookahead available
			Curly_Open,
			Curly_Close,
			Squared_Open,
			Squared_Close,
			Colon,
			Comma,
			String,
			Number,
			True,
			False,
			Null
		}

		readonly char[] json;
		readonly StringBuilder s = new StringBuilder();
		Token lookAheadToken = Token.None;
		int index;

		internal JsonParser(string json)
		{
			this.json = json.ToCharArray();
		}

		public object Decode()
		{
			return ParseValue();
		}

		private Dictionary<string, object> ParseObject()
		{
			Dictionary<string, object> table = new Dictionary<string, object>();

			ConsumeToken(); // {

			while (true)
			{
				switch (LookAhead())
				{

					case Token.Comma:
						ConsumeToken();
						break;

					case Token.Curly_Close:
						ConsumeToken();
						return table;

					default:
						{

							// name
							string name = ParseString();

							// :
							if (NextToken() != Token.Colon)
							{
								throw new Exception("Expected colon at index " + index);
							}

							// value
							object value = ParseValue();

							table[name] = value;
						}
						break;
				}
			}
		}

#if SILVERLIGHT
		private List<object> ParseArray()
		{
			List<object> array = new List<object>();
#else
		private ArrayList ParseArray()
		{
			ArrayList array = new ArrayList();
#endif
			ConsumeToken(); // [

			while (true)
			{
				switch (LookAhead())
				{

					case Token.Comma:
						ConsumeToken();
						break;

					case Token.Squared_Close:
						ConsumeToken();
						return array;

					default:
						{
							array.Add(ParseValue());
						}
						break;
				}
			}
		}

		private object ParseValue()
		{
			switch (LookAhead())
			{
				case Token.Number:
					return ParseNumber();

				case Token.String:
					return ParseString();

				case Token.Curly_Open:
					return ParseObject();

				case Token.Squared_Open:
					return ParseArray();

				case Token.True:
					ConsumeToken();
					return true;

				case Token.False:
					ConsumeToken();
					return false;

				case Token.Null:
					ConsumeToken();
					return null;
			}

			throw new Exception("Unrecognized token at index" + index);
		}

		private string ParseString()
		{
			ConsumeToken(); // "

			s.Length = 0;

			int runIndex = -1;

			while (index < json.Length)
			{
				var c = json[index++];

				if (c == '"')
				{
					if (runIndex != -1)
					{
						if (s.Length == 0)
							return new string(json, runIndex, index - runIndex - 1);

						s.Append(json, runIndex, index - runIndex - 1);
					}
					return s.ToString();
				}

				if (c != '\\')
				{
					if (runIndex == -1)
						runIndex = index - 1;

					continue;
				}

				if (index == json.Length) break;

				if (runIndex != -1)
				{
					s.Append(json, runIndex, index - runIndex - 1);
					runIndex = -1;
				}

				switch (json[index++])
				{
					case '"':
						s.Append('"');
						break;

					case '\\':
						s.Append('\\');
						break;

					case '/':
						s.Append('/');
						break;

					case 'b':
						s.Append('\b');
						break;

					case 'f':
						s.Append('\f');
						break;

					case 'n':
						s.Append('\n');
						break;

					case 'r':
						s.Append('\r');
						break;

					case 't':
						s.Append('\t');
						break;

					case 'u':
						{
							int remainingLength = json.Length - index;
							if (remainingLength < 4) break;

							// parse the 32 bit hex into an integer codepoint
							uint codePoint = ParseUnicode(json[index], json[index + 1], json[index + 2], json[index + 3]);
							s.Append((char)codePoint);

							// skip 4 chars
							index += 4;
						}
						break;
				}
			}

			throw new Exception("Unexpectedly reached end of string");
		}

		private uint ParseSingleChar(char c1, uint multipliyer)
		{
			uint p1 = 0;
			if (c1 >= '0' && c1 <= '9')
				p1 = (uint)(c1 - '0') * multipliyer;
			else if (c1 >= 'A' && c1 <= 'F')
				p1 = (uint)((c1 - 'A') + 10) * multipliyer;
			else if (c1 >= 'a' && c1 <= 'f')
				p1 = (uint)((c1 - 'a') + 10) * multipliyer;
			return p1;
		}

		private uint ParseUnicode(char c1, char c2, char c3, char c4)
		{
			uint p1 = ParseSingleChar(c1, 0x1000);
			uint p2 = ParseSingleChar(c2, 0x100);
			uint p3 = ParseSingleChar(c3, 0x10);
			uint p4 = ParseSingleChar(c4, 1);

			return p1 + p2 + p3 + p4;
		}

		private string ParseNumber()
		{
			ConsumeToken();

			// Need to start back one place because the first digit is also a token and would have been consumed
			var startIndex = index - 1;

			do
			{
				var c = json[index];

				if ((c >= '0' && c <= '9') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E')
				{
					if (++index == json.Length) throw new Exception("Unexpected end of string whilst parsing number");
					continue;
				}

				break;
			} while (true);

			return new string(json, startIndex, index - startIndex);
		}

		private Token LookAhead()
		{
			if (lookAheadToken != Token.None) return lookAheadToken;

			return lookAheadToken = NextTokenCore();
		}

		private void ConsumeToken()
		{
			lookAheadToken = Token.None;
		}

		private Token NextToken()
		{
			var result = lookAheadToken != Token.None ? lookAheadToken : NextTokenCore();

			lookAheadToken = Token.None;

			return result;
		}

		private Token NextTokenCore()
		{
			char c;

			// Skip past whitespace
			do
			{
				c = json[index];

				if (c > ' ') break;
				if (c != ' ' && c != '\t' && c != '\n' && c != '\r') break;

			} while (++index < json.Length);

			if (index == json.Length)
			{
				throw new Exception("Reached end of string unexpectedly");
			}

			c = json[index];

			index++;

			//if (collection >= '0' && collection <= '9')
			//    return Token.Number;

			switch (c)
			{
				case '{':
					return Token.Curly_Open;

				case '}':
					return Token.Curly_Close;

				case '[':
					return Token.Squared_Open;

				case ']':
					return Token.Squared_Close;

				case ',':
					return Token.Comma;

				case '"':
					return Token.String;

				case '0': case '1': case '2': case '3': case '4':
				case '5': case '6': case '7': case '8': case '9':
				case '-': case '+': case '.':
					return Token.Number;

				case ':':
					return Token.Colon;

				case 'f':
					if (json.Length - index >= 4 &&
						json[index + 0] == 'a' &&
						json[index + 1] == 'l' &&
						json[index + 2] == 's' &&
						json[index + 3] == 'e')
					{
						index += 4;
						return Token.False;
					}
					break;

				case 't':
					if (json.Length - index >= 3 &&
						json[index + 0] == 'r' &&
						json[index + 1] == 'u' &&
						json[index + 2] == 'e')
					{
						index += 3;
						return Token.True;
					}
					break;

				case 'n':
					if (json.Length - index >= 3 &&
						json[index + 0] == 'u' &&
						json[index + 1] == 'l' &&
						json[index + 2] == 'l')
					{
						index += 3;
						return Token.Null;
					}
					break;

			}

			throw new Exception("Could not find token at index " + --index);
		}
	}
}
